#!/usr/bin/python

#
#Stefano Dal Pra
#stefano.dalpra@cnaf.infn.it, s.dalpra@gmail.com
#

import os,sys,time,re,random
import smtplib
import getpass,commands,json

"""
[sdalpra@ui01-lcg ~]$ 
for file in `/opt/storm/srm-clients/bin/clientSRM 
ls -e httpg://storm-fe-archive.cr.cnaf.infn.it:8444 
-s srm://storm-fe-archive.cr.cnaf.infn.it:8444/srm/managerv2?SFN=
/dteam/sdalpra/ | 
grep path | sed -e '1d'|awk -F'"' '{print $2}' ` ; 
do /opt/storm/srm-clients/bin/clientSRM rm 
-e httpg://storm-fe-archive.cr.cnaf.infn.it:8444 
-s srm://storm-fe-archive.cr.cnaf.infn.it:8444/srm/managerv2?SFN=$file ; 
done 
"""


"""
{ep:
}
"""

def now():
    return time.ctime(time.time())

def mlog(f,m,dbg=True):
    if dbg:
        f.write("%s: "%now()+m+'\n')
        f.flush()

def mailsend(mailconf, par = {'subjadd':'','bodyadd':''}):
    """mailsend({'From':'from@some.where','To':'some@dest',....},\n\
{'subjadd':'more subj details','bodyadd':'more body details'})"""
    d = mailconf
    d['Subject'] += par['subjadd']
    d['Body'] += par['bodyadd']
    d['Date: '] = now()
    DS = d.pop('mailserver')
    Text = d.pop('Body')
    mMessage = '\n'.join([k+': '+v for k,v in d.iteritems()]) + '\n' + Text
    s = smtplib.SMTP(DS)
    rCode = s.sendmail(d['From'], d['To'], mMessage)
    s.quit()
    if rCode:
        return "Errore nell'invio del messaggio"
    else:
        return 'Messaggio inviato con successo'

class StormChecker(object):
    def __getproxypass(self):
        try:
            return self.__proxypass
        except AttributeError:
            self.__proxypass = getpass.getpass("proxypass: ")
    def proxy(self):
        self.proxy_ok()
        if self.Tleft > 600:
            self.Tleft -= (time.time() - self.T0)
            self.T0 = time.time()
            return self.Tleft, "proxy ok"
        self.__getproxypass()
        e,o=commands.getstatusoutput(
            "echo %s | voms-proxy-init -pwstdin --voms dteam"%self.__proxypass)
        if e != 0 :
            mlog(self.log,"Errore creazione proxy. Esco ")
            sys.exit()
        self.Tleft = 3600 * 12
        self.T0 = time.time()
        return 0,'Proxy ok'
    def proxy_ok(self):
        e,o = commands.getstatusoutput("voms-proxy-info")
        if e != 0:
            self.Tleft = 0
            return 0,"no valid proxy"
        l = o.split('\n')[-1].rstrip()
        L = [int(x) for x in l.rstrip().split(':')[1:]]
        self.Tleft = L[0]*3600 + L[1]*60 + L[2]
        return self.Tleft, "proxy expires in %s hms"%':'.join([str(x) for x in L])
    def __init__(self,conf_file):
        print "Reading Conf from %s"%conf_file
        self.conf_file = conf_file
        self.conf = json.read(open(self.conf_file,'r').read())
        #print repr(self.conf)	
        print "FINITO!"
        if self.conf['logfile'] == '-':
            print "logging to stdout"
            self.log = sys.stdout
        else:
            try:
                print "logging to %s"%self.conf['logfile']
                self.log = open(self.conf['logfile'],'a')
            except IOError:
                print "cannot open %s for append. Using stdout"%self.conf['logfile']
                self.log = open(sys.stdout,'a')
        if self.conf['errlogfile'] == '-':
            print "logging errors to stderr"
            self.elog = sys.stderr
        else:
            try:
                print "logging to %s"%self.conf['errlogfile']
                self.elog = open(self.conf['errlogfile'],'a')
            except IOError:
                print "cannot open %s for append. Using stdout"%self.conf['errlogfile']
                self.elog = open(sys.stdout,'a')
        mlog(self.log,now()+"testlog\n")
        self.Tleft = 0
        self.T0 = time.time()
        if self.conf['opt_ck_proxy']:
            self.proxy_ok()
            if self.Tleft < 600 :
                self.__getproxypass()
                self.proxy()
        self.__n = 1 
        self.__Ncmd = len(self.conf['actions'])
        d=self.cmd={}
        self.cmdout={}
        self.cmdoutlist=[]
        self.conf['workdir'] = self.conf['workdir'].rstrip('/')+('_%s/'%os.getpid()) * self.conf['workdir_is_common']
        for k,v in self.conf['epl'].iteritems():
            d[k] = {
            'ep':"%s://%s.%s:%d"%(self.conf['epprotocol'],v[0],
                self.conf['epdomain'],v[1]),
            'bs':"srm://%s.%s:%d%s"%(v[0],self.conf['epdomain'],
                v[1],v[2]),
            'bp':v[3]
                }
        self.funmap={}
        for k,v in self.conf['act2fun'].iteritems():
            self.funmap[k] = eval('self.'+v)
        self.si = self.cmd.keys()[0]
        self.X = {'dstfilename':self.conf['dstfilename']}
        self.mailstat = {'num':0,'tsend':0}
        
    def nap(self):
        n = self.conf['naptime']
        mlog(self.log,"sleeping %d seconds"%n)
        time.sleep(n)
        return 0,'sleep done'
    def cleanout(self,o):
        L=[x.strip() for x in o.split('\n')]
        self.cmdout = {}
        p = re.compile('^(statusCode=|requestToken=|explanation=|.*TURL=)')
        for l in L:
            m = p.match(l)
            if not m : continue
            k = l[m.start():m.end()-1].split()[-1]
            v = l[m.end():].split('"')[1]
            self.cmdout[k] = v
        return self.cmdout
    def ls(self):
        d = self.cmd[self.si]
        dc = self.conf
        c = "%s ls -e %s -s %s%s%s%s"%(
            dc['cli'],d['ep'],d['bs'],d['bp'],dc['workdir'],self.X['targetfilename'])
        mlog(self.log,"LS: %s"%c)
        return commands.getstatusoutput(c)
    def lsdir(self):
        rg = re.compile('path="(.*)"')
        d = self.cmd[self.si]
        dc = self.conf
        c = "%s ls -e %s -s %s%s%s"%(
            dc['cli'],d['ep'],d['bs'],d['bp'],dc['workdir'])
        mlog(self.log,"LS: %s"%c,True)
        e,o = commands.getstatusoutput(c)
        if e == 0:
            self.X['lsdir'] = rg.findall(o)
            self.X['lsdir'].sort(cmp=lambda x,y:len(x)-len(y))
            try:
                self.X['basedir'] = self.X['lsdir'].pop(0)
            except IndexError:
                self.X['lsdir'] = None
                e = 1
        else: self.X['lsdir'] = None
        return e,o
    def rm(self):
        d = self.cmd[self.si]
        dc = self.conf
        c = "%s rm -e %s -s %s%s%s%s"%(
            dc['cli'],d['ep'],d['bs'],d['bp'],dc['workdir'],
            os.path.basename(self.X['targetfilename']))
        mlog(self.log,"RM: %s"%c)
        return commands.getstatusoutput(c)
    def mkdir(self):
        d = self.cmd[self.si]
        dc = self.conf
        c = "%s mkdir -e %s -s %s%s%s"%(
            dc['cli'],d['ep'],d['bs'],d['bp'],dc['workdir'])
        mlog(self.log,"MKDIR: %s"%c)
        return commands.getstatusoutput(c)
    def rmnonemptydir(self):
        d = self.cmd[self.si]
        dc = self.conf
        if not self.X.has_key('lsdir'):
            self.lsdir()
        for abspathfile in self.X['lsdir']:
            self.X['targetfilename'] = abspathfile
            self.rm()
            c = "%s rm -e %s -s %s%s"%(
                dc['cli'],d['ep'],d['bs'],abspathfile)
            mlog(self.log,"DELETING %s:\n%s"%(abspathfile,c))
        if self.X['lsdir']: return commands.getstatusoutput(c)
        return 0,'DIR Empty'
    def rmdir(self):
        d = self.cmd[self.si]
        dc = self.conf
        c = "%s rmdir -e %s -s %s%s%s"%(
            dc['cli'],d['ep'],d['bs'],d['bp'],dc['workdir'])
        mlog(self.log,"RMDIR: %s"%c)
        return commands.getstatusoutput(c)        
    def ptp(self):
        d = self.cmd[self.si]
        dc = self.conf
        c = "%s ptp -e %s -s %s%s%s%s -p"%(
            dc['cli'],d['ep'],d['bs'],d['bp'],dc['workdir'],self.X['targetfilename'])
        mlog(self.log,"PTP: %s"%c)
        return commands.getstatusoutput(c)
    def ptg(self):
        #print repr(self.X)
        d = self.cmd[self.si]
        dc = self.conf
        try:
            self.X['targetfilename'] = os.path.basename(random.choice(self.X['lsdir']))
        except IndexError:
            mlog(self.log,"PTG FALLITA, no files found: ")            
        c = "%s ptg -e %s -s %s%s%s%s -p"%(
            dc['cli'],d['ep'],d['bs'],d['bp'],dc['workdir'],self.X['targetfilename'])
        mlog(self.log,"PTG: %s"%c)
        return commands.getstatusoutput(c)
    def pd(self): 
        d = self.cmd[self.si]
        dc = self.conf
        try:
            c = "%s pd -e %s -s %s%s%s%s -t %s"%(
                dc['cli'],d['ep'],d['bs'],d['bp'],dc['workdir'],self.X['targetfilename'],
                self.cmdoutlist[-2]['requestToken'])
        except KeyError:
            print 'PD KEYERROR: ' + '-'*40
            print repr(self.cmdoutlist)
            raise
        mlog(self.log,"PD: %s"%c)
        return commands.getstatusoutput(c)
    def rf(self): 
        d = self.cmd[self.si]
        dc = self.conf
        try:
            c = "%s rf -e %s -t %s"%(
                dc['cli'],d['ep'],
                self.cmdoutlist[-1]['requestToken'])
        except KeyError:
            return 'CMDLINE_FAILED', 1
        mlog(self.log,"RF: %s"%c)
        return commands.getstatusoutput(c)
    def globuscopy(self):
        d = self.cmd[self.si]
        dc = self.conf
        try:
            c = "globus-url-copy -vb file://%s %s"%(
                        self.conf['srcfilename'],
                        self.cmdoutlist[-1]['TURL'])
        except KeyError:
            return -1,now()+": globus-url-copy fallita"
        mlog(self.log,"GLOBUSCOPY: %s"%c)
        return commands.getstatusoutput(c)
    def globusgetfile(self):
        d = self.cmd[self.si]
        dc = self.conf
        try:
            c = "globus-url-copy -vb %s file://%s "%(
                self.cmdoutlist[-1]['TURL'],
                self.conf['localdest']
                )
        except KeyError:
            return -1,now()+": globus-url-copy fallita"
        mlog(self.log,"GLOBUSCOPY: %s"%c)
        return commands.getstatusoutput(c)
    def run(self):
        ncicli,stop = 0,0
        while ncicli < self.conf['numcycles']:
            mlog(self.log,"CICLO %d [%s]"%(ncicli,now())) 
            cycletime = time.time()
            for self.si in self.cmd.keys():
                if stop : 
                    stop = 0
                    break
                for act in self.conf['actions']:
                    if stop : break
                    fun = self.funmap[act]
                    mlog(self.log,"--> act = %s"%act)
                    self.X['targetfilename'] = self.X['dstfilename']+"_%f"%cycletime
                    e,o = fun()
                    d = self.cleanout(o)
                    mlog(self.log,"-"*80)
                    mlog(self.log,o)
                    mlog(self.log,"-"*80)
                    d['cmdretCode'] = {False:'CMDLINE_FAILED',True:'CMDLINE_OK'}[e==0]
                    try:
                        mlog(self.log,'%s:%s'%(d['statusCode'],d['explanation']))
                    except KeyError:
                        mlog(self.log,"No SRM statuscode, return code = %d"%e,True)
                        d['statusCode'] = 'NO_SRM_STATUS'
                    if not act in ('proxy') and d['cmdretCode'] != 'SRM_SUCCESS':
                        mlog(self.log,"%s no srm_success: %d, output:\n%s"%(act,e,o),True)
                        d['cmdretCode'] = 'CMDLINE_FAILED'
                    if d['cmdretCode'] == 'CMDLINE_FAILED' or (
                        d['statusCode'] == 'NO_SRM_STATUS' and not act in ('proxy','fput','sleep','emptydir')):
                        try:
                            if d['statusCode'] in ['SRM_INTERNAL_ERROR','CMDLINE_FAILED','NO_SRM_STATUS'] : 
                                tnow = time.time()
                                if self.mailstat['num'] and tnow - self.mailstat['tsend'] < 60 :
                                    self.mailstat['num']+=1
                                    mlog(self.log,"Sleeping 60 secs")
                                    time.sleep(60)
                                    continue
                                self.mailstat['tsend'] = tnow
                                mlog(self.elog,"%s ALLARME: %s : %s"%(act,d['statusCode'],d['explanation']))
                                mlog(self.elog,"cmd out:\n%s"%o)
                                mlog(self.elog,"sending email alert; restarting cycle")
                                maildetail = '-' * 10 + '\n\n' + o
                                par = {'subjadd' : '[#%d] '%self.mailstat['num'] + act+' '+self.si,
                                        'bodyadd' : 
                                            '\n%s for %s\n%s\n\n%s%s%s%s'%(
                                            d['statusCode'],self.si,o,'-'*40+'\n',repr(self.cmdoutlist),
                                            '-'*40+'\n',repr(self.X))
                                }
                                result = mailsend(self.conf['email'].copy(),par)
                                mlog(self.elog,result)
                                stop = 1
                                self.nap()
                        except KeyError:
                            pass
                    self.cmdoutlist.append(d)
            ncicli += 1
            self.cmdoutlist = []

    def emptydir(self):
        commands.getstatusoutput()

def main():
    try:
        S=StormChecker(sys.argv[1])
    except:
        "Usage: python health.py conf_file.json"
        sys.exit(0)
    S.run()

if __name__ == '__main__':
      main()
      
